package command

import (
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"
	agentv1beta1 "gopkg.openfuyao.cn/bkeagent/api/v1beta1"
	bkenode "gopkg.openfuyao.cn/bkecommon/cluster/node"

	"gopkg.openfuyao.cn/cluster-api-provider-bke/utils"
)

type HA struct {
	BaseCommand
	MasterNodes              bkenode.Nodes
	IngressNodes             bkenode.Nodes
	IngressVIP               string
	ControlPlaneEndpointPort int32
	ControlPlaneEndpointVIP  string
	ImageRepo                string
	ManifestsDir             string
	VirtualRouterId          string
	WaitVIP                  bool

	isMasterHa bool
}

func (l *HA) Validate() error {
	if l.ImageRepo == "" {
		return errors.New("ImageRepo is empty")
	}
	if l.ManifestsDir == "" {
		return errors.New("manifestsDir is empty")
	}

	if l.MasterNodes.Length() != 0 && l.IngressNodes.Length() != 0 {
		return errors.New("loadbalance command except configure one type of Ha, but both types (master ha,ingress ha) are configured")

	}

	if l.MasterNodes.Length() != 0 {
		l.isMasterHa = true
		if l.ControlPlaneEndpointPort == 0 {
			return errors.New("controlPlaneEndpointPort is empty")
		}
		if l.ControlPlaneEndpointVIP == "" {
			return errors.New("controlPlaneEndpointVIP is empty")
		}
	}
	if l.IngressNodes.Length() != 0 {
		l.isMasterHa = false
		if l.IngressVIP == "" {
			return errors.New("ingressVIP is empty")
		}
	}

	if l.MasterNodes.Length() == 0 && l.IngressNodes.Length() == 0 {
		return errors.New("loadbalance command except at least one node but got 0")
	}

	return l.BaseCommand.validate()
}

func (l *HA) New() error {
	if err := l.Validate(); err != nil {
		return err
	}

	manifestsDir := fmt.Sprintf("manifestsDir=%s", l.ManifestsDir)
	imageRepo := fmt.Sprintf("imageRepo=%s", l.ImageRepo)

	commandName := fmt.Sprintf("%s-%d", HACommandName, time.Now().Unix())
	commandSpec := GenerateDefaultCommandSpec()

	var haNodesLi []string
	if l.isMasterHa {
		for _, node := range l.MasterNodes {
			haNodesLi = append(haNodesLi, fmt.Sprintf("%s:%s", node.Hostname, node.IP))
		}
		haNodes := fmt.Sprintf("haNodes=%s", strings.Join(haNodesLi, ","))
		controlPlaneEndpointPort := fmt.Sprintf("controlPlaneEndpointPort=%d", l.ControlPlaneEndpointPort)
		controlPlaneEndpointVIP := fmt.Sprintf("controlPlaneEndpointVIP=%s", l.ControlPlaneEndpointVIP)
		virtualRouterId := fmt.Sprintf("virtualRouterId=%s", l.VirtualRouterId)
		waitVIP := fmt.Sprintf("wait=%t", l.WaitVIP)
		commandSpec.Commands = []agentv1beta1.ExecCommand{
			{
				ID: "master-ha",
				Command: []string{
					"HA",
					haNodes,
					controlPlaneEndpointPort,
					controlPlaneEndpointVIP,
					virtualRouterId,
					imageRepo,
					manifestsDir,
					waitVIP,
				},
				Type: agentv1beta1.CommandBuiltIn,
			},
		}
		commandSpec.NodeSelector = getNodeSelector(l.MasterNodes)
	} else {
		for _, node := range l.IngressNodes {
			haNodesLi = append(haNodesLi, fmt.Sprintf("%s:%s", node.Hostname, node.IP))
		}
		haNodes := fmt.Sprintf("haNodes=%s", strings.Join(haNodesLi, ","))
		ingressVIP := fmt.Sprintf("ingressVIP=%s", l.IngressVIP)
		virtualRouterId := fmt.Sprintf("virtualRouterId=%s", l.VirtualRouterId)
		waitVIP := fmt.Sprintf("wait=%t", l.WaitVIP)
		commandSpec.Commands = []agentv1beta1.ExecCommand{
			{
				ID: "ingress-ha",
				Command: []string{
					"HA",
					haNodes,
					ingressVIP,
					imageRepo,
					virtualRouterId,
					manifestsDir,
					waitVIP,
				},
				Type: agentv1beta1.CommandBuiltIn,
			},
		}
		commandSpec.NodeSelector = getNodeSelector(l.IngressNodes)
	}

	return l.newCommand(commandName, BKEClusterLabel, commandSpec)
}

func (l *HA) Wait() (error, []string, []string) {
	err, complete, successNodes, failedNodes := l.waitCommandComplete()
	// means all command not executed
	if !complete && len(failedNodes) == 0 {
		for _, node := range l.MasterNodes {
			if utils.ContainsString(successNodes, node.Hostname) {
				continue
			}
			failedNodes = append(failedNodes, node.Hostname)
		}
	}
	return err, successNodes, failedNodes
}
